﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MapReduceLib.DataModel;
using log4net;
using System.IO;
using System.Runtime.Serialization;
using Ionic.Zip;
using MasterNode.Interfaces;
using System.Xml;
using MapReduceLib.DataModel.DataSources;

namespace MasterNode.ServiceImpl
{
    class JobRepository : IJobRepository
    {
        private static ILog log = LogManager.GetLogger(typeof(JobRepository));
        private const string ManifestFileName = "job.xml";
        private List<Job> registeredJobs;

        public JobRepository()
        {
            registeredJobs = new List<Job>();
            log.Debug("Reading existing jobs...");

            Job j = new Job();
            j.AssemblyFileName = "a.dll";
            DirectoryDataSource ds = new DirectoryDataSource();
            j.DataSource = ds;
            j.JobName = "Job With Data";
            j.JobVersion = "1.1";
            j.UserParameters.Add("Param1");
            UpdateJob("C:\\job.xml", j);


            registeredJobs = ReadExistingJobs();
        }

        public Job AddJob(byte[] archivedJobBytes)
        {
            Job job = null;
            bool success = true;
            string jobId = Guid.NewGuid().ToString();
            DirectoryInfo jobDir = Directory.CreateDirectory(string.Format("./Jobs/{0}", jobId));

            try
            {
                using (ZipFile zipFile = ZipFile.Read(archivedJobBytes))
                {
                    if (!zipFile.ContainsEntry(ManifestFileName))
                    {
                        success = false;
                        log.Debug("Provided archive isn't a valid job archive");
                        throw new Exception("Bad archive");
                    }

                    zipFile[ManifestFileName].Extract(jobDir.FullName);
                    job = ReadJob(Path.Combine(jobDir.FullName, ManifestFileName));
                    log.DebugFormat("Deploying job with ID {0}", jobId);

                    if (!zipFile.ContainsEntry(job.AssemblyFileName))
                    {
                        success = false;
                        log.DebugFormat("Assembly file {0} not found inside archive", job.AssemblyFileName);
                        throw new Exception("Bad archive");
                    }

                    job.JobId = jobId;
                    zipFile[job.AssemblyFileName].Extract(jobDir.FullName);
                    job.AssemblyBytes = File.ReadAllBytes(Path.Combine(jobDir.FullName, job.AssemblyFileName));
                    UpdateJob(Path.Combine(jobDir.FullName, ManifestFileName), job);
                    registeredJobs.Add(job);
                }
            }
            catch (Exception e)
            {
                success = false;
                log.Error(e.Message, e);
                throw e;
            }
            finally
            {
                if (!success)
                    Directory.Delete(jobDir.FullName, true);
            }

            return job;
        }

        public IEnumerable<Job> RegisteredJobs
        {
            get { return registeredJobs; }
        }

        public Job GetJob(string jobId)
        {
            return registeredJobs.FirstOrDefault(x => x.JobId == jobId);
        }

        private List<Job> ReadExistingJobs()
        {
            if (!Directory.Exists("./Jobs"))
                Directory.CreateDirectory("./Jobs");

            List<Job> result = new List<Job>();

            foreach (string jobDirPath in Directory.GetDirectories("./Jobs"))
            {
                try
                {
                    Job job = ReadJob(Path.Combine(jobDirPath, ManifestFileName));

                    log.DebugFormat("Sucessfully read job with ID {0}", job.JobId);
                    result.Add(job);
                }
                catch (Exception e)
                {
                    log.Error(e.Message, e);
                    log.DebugFormat("Invalid job detected in {0}, deleting directory", jobDirPath);
                    Directory.Delete(jobDirPath, true);
                }
            }

            return result;
        }

        private Job ReadJob(string path)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(Job));

            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                return serializer.ReadObject(fs) as Job;
            }
        }

        private void UpdateJob(string path, Job updatedJob)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(Job));

            var settings = new XmlWriterSettings { Indent = true };

            using (var writer = XmlWriter.Create(path, settings))
            {
                serializer.WriteObject(writer, updatedJob);
            }
        }
    }
}
